#include "Basic/Stream.h"
#include "Basic/Exceptions.h"
#include "Share.h"
#include "Basic/FileSystem.h"
#include "Basic/StringBuilder.h"
namespace GxLibBasic
{

	int ContainsBOM(String FileName)
	{
		FILE * f;
		_wfopen_s(&f,FileName.GetStrPtr(),L"rb");
		if (!f)
			return -1;
		Byte header[3];
		fread(header,1,3,f);
		fclose(f);
		if (header[0] == 0xFF && header[1] == 0xFE)
			return 2;
		else if (header[0] == 0xFE && header[1] == 0xFF)
			return 2;
		else if (header[0] == 0xEF && header[1] == 0xBB && header[2] == 0xBF)
			return 3;
		else
			return 0;
	}

	int GetTextEncoding(String FileName)
	{
		FILE * f;
		_wfopen_s(&f,FileName.GetStrPtr(),L"rb");
		if (!f)
			return -1;
		Byte header[3];
		fread(header,1,3,f);
		fclose(f);
		if (header[0] == 0xFF && header[1] == 0xFE)
			return gxteUTF16;
		else if (header[0] == 0xFE && header[1] == 0xFF)
			return gxteUTF16_BigEndian;
		else if (header[0] == 0xEF && header[1] == 0xBB && header[2] == 0xBF)
			return gxteUTF8;
		else if (header[1] == 0)
			return gxteUTF8;
		else
			return gxteANSI;
	}

	String ReadTextFile(const String & fileName)
	{
		TextFileStream fs(fileName, gxfmRead);
		
		StringBuilder rs(65536);
		while (!fs.IsEnd())
			rs.WriteString(fs.GetLine());
		return rs.GetStrPtr();
	}

	void WriteTextFile(const String & fileName, const String & text)
	{
		TextFileStream fs(fileName, gxfmWrite);
		fs.WriteString(text);
	}


	void Stream::CopyFrom(Stream *s)
	{
		int size = s->Size();
		char * buf = new char [size];
		try
		{
			int p = s->GetPosition();
			s->Seek(gxsoBegin,0);
			s->ReadBuffer(buf,1,size);
			s->SetPosition(p);
			WriteBuffer(buf,1,size);
			delete [] buf;
		}
		catch(...)
		{
			delete [] buf;
			throw;
		}
		
	}

	MemoryStream::MemoryStream(const char * buf, int len)
	{
		FBuf.SetSize(len);
		for (int i=0; i<len; i++)
			FBuf[i] = (buf[i]);
		FPtr = 0;
	}
	MemoryStream::MemoryStream()
	{
		FPtr = 0;
	}
	void MemoryStream::Seek(gxSeekOrigin origin, Int Offset)
	{
		if (origin == gxsoCurrent)
		{
			FPtr += Offset;
		}
		else if (origin == gxsoBegin)
		{
			FPtr = Offset;
		}
		else
		{
			FPtr = FBuf.Count() - Offset;
		}
		if (FPtr<0 || FPtr > FBuf.Count())
		{
			throw gxIndexOutOfRangeException();
		}
	}
	Int MemoryStream::GetPosition()const
	{
		return FPtr;
	}
	void MemoryStream::SetPosition(Int Pos)
	{
		FPtr = Pos;
		if (FPtr<0 || FPtr > FBuf.Count())
		{
			throw gxIndexOutOfRangeException();
		}
	}
	Int MemoryStream::Size()const
	{
		return FBuf.Count();
	}
	Int MemoryStream::ReadBuffer(void *buf, Int element_size, Int count)
	{
		memcpy(buf, FBuf.Buffer()+FPtr, count*element_size);
		FPtr += count*element_size;
		return count*element_size;
	}
	Int MemoryStream::WriteBuffer(const void *buf, Int element_size, Int count)
	{
		if (FPtr+count*element_size >= FBuf.Count())
		{
			FBuf.SetSize(FPtr+count*element_size+1);
		}
		memcpy(FBuf.Buffer()+FPtr, buf, count*element_size);
		FPtr += count*element_size;
		return count*element_size;
	}
	bool MemoryStream::IsEnd()const
	{
		return FPtr == FBuf.Count();
	}
	char* MemoryStream::Buffer()
	{
		return FBuf.Buffer();
	}

	FileStream::FileStream(String FileName, gxFileStreamMode mode, gxFileShareMode shr, bool Text, int Encoding)
	{
		String m;
		int share;
		switch(mode)
		{
		case gxfmRead:
			m = "r";
			break;
		case gxfmWrite:
			m = "w";
			break;
		case gxfmReadWithWrite:
			m = "r+";
			break;
		case gxfmWriteWithRead:
			m = "w+";
			break;
		case gxfmAppend:
			m = "a+";
			break;
		default:
			m = "r+";
			break;
		}
		switch (shr)
		{
		case gxfmDenyNo:
			share = _SH_DENYNO;
			break;
		case gxfmDenyRead:
			share = _SH_DENYRD;
			break;
		case gxfmDenyWrite:
			share = _SH_DENYWR;
			break;
		case gxfmDenyReadWrite:
			share = _SH_DENYRW;
			break;
		default:
			share = _SH_DENYNO;
			break;
		}
		int bomSize = 0;
		if (Text)
		{
			if (mode == gxfmRead)
			{
				FTextEncoding = GetTextEncoding(FileName);
				if (FTextEncoding == -1)
					FTextEncoding = Encoding;
				switch (FTextEncoding)
				{
				case gxteANSI:
					m += "t";
					break;
				case gxteUTF8:
					m += "b,ccs=UTF-8";
					break;
				case gxteUTF16:
					m += "b,ccs=UTF-16LE";
					break;
				default:
					throw Exception(L"Support for UTF-16 big endian not implemented.",L"",0);
				}
				bomSize = ContainsBOM(FileName);
			}
			else
			{
				FTextEncoding = gxteANSI;
				m += "t";
			}
		}
		else
			m += "b";
		_wfopen_s(&FHandle,FileName.GetStrPtr(),m.GetStrPtr());
		if (!FHandle)
			throw gxFileSystemException(FileName,__FILE__,__LINE__);
		if (bomSize)
			fseek(FHandle, bomSize, SEEK_SET);
	}

	FileStream::~FileStream()
	{
		if (FHandle)
			fclose(FHandle);
	}

	void FileStream::Seek(gxSeekOrigin origin, Int Offset) 
	{
		int ori;
		switch(Offset)
		{
		case gxsoCurrent:
			ori = SEEK_CUR;
			break;
		case gxsoBegin:
			ori = SEEK_SET;
			break;
		default:
			ori = SEEK_END;
		};
		fseek(FHandle, Offset, ori);
	}

	Int FileStream::GetPosition() const
	{
		fpos_t p;
		fgetpos(FHandle, &p);
		return (Int)p;
	}

	Int FileStream::Size() const
	{
		fpos_t p;
		fgetpos(FHandle, &p);
		fseek(FHandle, 0, SEEK_END);
		fpos_t p1;
		fgetpos(FHandle, &p1);
		fsetpos(FHandle, &p);
		return (int)p1;
	}

	bool FileStream::IsEnd() const
	{
		return feof(FHandle)!=0;
	}

	void FileStream::SetPosition(Int Pos)
	{
		fpos_t p = Pos;
		fsetpos(FHandle, &p);
	}

	Int FileStream::ReadBuffer(void *buf, Int element_size, Int count)
	{
		return fread_s(buf,element_size*count, element_size,count,FHandle);
	}

	Int FileStream::WriteBuffer(const void *buf, Int element_size, Int count)
	{
		return fwrite(buf,element_size, count, FHandle);
	}

	TextFileStream::TextFileStream(String FileName, gxFileStreamMode mode, gxFileShareMode share, int Encoding)
	: FileStream(FileName, mode, share, true, Encoding)
	{};

	wchar_t TextFileStream::ReadChar()
	{
		if (FTextEncoding != gxteANSI)
			return fgetwc(FHandle);
		else
		{
			char buf[2];
			buf[0] = fgetc(FHandle);
			buf[1] = 0;
			String s = buf;
			return s[0];
		}
	}

	String TextFileStream::GetLine()
	{
		const Int mcount = 512;
		String rs;
		if (FTextEncoding != gxteANSI)
		{
			wchar_t buf[mcount];
			memset(buf,0,mcount*sizeof(wchar_t));
			fgetws(buf, mcount-1, FHandle);
			rs = buf;
			while (buf[wcslen(buf)-1]!= L'\n' && ! feof(FHandle))
			{
				memset(buf,0,mcount*sizeof(wchar_t));
				fgetws(buf, mcount-1, FHandle);
				rs += buf;
			}
		}
		else
		{
			char cbuf[mcount];
			memset(cbuf,0,mcount*sizeof(char));
			fgets(cbuf, mcount-1, FHandle);
			rs = cbuf;
			while (cbuf[strlen(cbuf)-1]!= L'\n' && ! feof(FHandle))
			{
				memset(cbuf,0,mcount*sizeof(char));
				fgets(cbuf, mcount-1, FHandle);
				rs += cbuf;
			}
		}
		return rs;
	}

	void TextFileStream::WriteChar(wchar_t c)
	{
		if (FTextEncoding != gxteANSI)
			fputwc(c, FHandle);
		else
		{
			String s = c;
			char ch = s.ToMBString()[0];
			fputc(ch,FHandle);
		}
	}

	void TextFileStream::WriteString(String s)
	{
		if (!s.GetLength())
			return;
		if (FTextEncoding != gxteANSI)
			fputws(s.GetStrPtr(), FHandle);
		else
			fputs(s.ToMBString(), FHandle);
	}
}